"""Tests for download tracking functionality with SQLite database.""" import pytest import tempfile import shutil from pathlib import Path from unittest.mock import patch, MagicMock from clients.garmin_client import GarminClient from db.session import SessionLocal from db.models import ActivityDownload, Base from config.settings import DATA_DIR, DB_PATH class TestDownloadTracking: """Test download tracking functionality.""" @pytest.fixture(autouse=True) def setup_and_teardown(self): """Set up test database and clean up after tests.""" # Create a temporary directory for test data self.test_data_dir = Path(tempfile.mkdtemp()) # Create test database self.test_db_path = self.test_data_dir / "test_garmin_analyser.db" # Patch settings to use test paths with patch('config.settings.DATA_DIR', self.test_data_dir), \ patch('config.settings.DB_PATH', self.test_db_path): # Initialize test database from db.session import engine Base.metadata.create_all(bind=engine) yield # Clean up if self.test_db_path.exists(): self.test_db_path.unlink() if self.test_data_dir.exists(): shutil.rmtree(self.test_data_dir) def test_upsert_activity_download_success(self): """Test upserting a successful download record.""" from clients.garmin_client import upsert_activity_download # Create test data activity_id = 12345 file_path = self.test_data_dir / "test_activity.fit" file_path.write_bytes(b"test file content") # Call the function with SessionLocal() as db: result = upsert_activity_download( activity_id=activity_id, source="garmin-connect", file_path=file_path, file_format="fit", status="success", size_bytes=len(file_path.read_bytes()), checksum_sha256="test_checksum", db_session=db ) # Verify the record was created record = db.query(ActivityDownload).filter_by(activity_id=activity_id).first() assert record is not None assert record.activity_id == activity_id assert record.source == "garmin-connect" assert record.status == "success" assert record.file_format == "fit" assert record.size_bytes == 18 # Length of "test file content" assert record.checksum_sha256 == "test_checksum" def test_upsert_activity_download_failure(self): """Test upserting a failed download record.""" from clients.garmin_client import upsert_activity_download activity_id = 67890 # Call the function with failure status with SessionLocal() as db: result = upsert_activity_download( activity_id=activity_id, source="garmin-connect", file_path=self.test_data_dir / "nonexistent.fit", file_format="fit", status="failed", error_message="Download timeout", http_status=500, db_session=db ) # Verify the record was created record = db.query(ActivityDownload).filter_by(activity_id=activity_id).first() assert record is not None assert record.activity_id == activity_id assert record.status == "failed" assert record.error_message == "Download timeout" assert record.http_status == 500 def test_upsert_activity_download_update_existing(self): """Test updating an existing download record.""" from clients.garmin_client import upsert_activity_download activity_id = 11111 # Create initial record with SessionLocal() as db: initial_record = ActivityDownload( activity_id=activity_id, source="garmin-connect", file_path=str(self.test_data_dir / "old.fit"), file_format="fit", status="success", size_bytes=100, checksum_sha256="old_checksum" ) db.add(initial_record) db.commit() # Update the record with SessionLocal() as db: result = upsert_activity_download( activity_id=activity_id, source="garmin-connect", file_path=self.test_data_dir / "new.fit", file_format="fit", status="success", size_bytes=200, checksum_sha256="new_checksum", db_session=db ) # Verify the record was updated record = db.query(ActivityDownload).filter_by(activity_id=activity_id).first() assert record is not None assert record.size_bytes == 200 assert record.checksum_sha256 == "new_checksum" def test_calculate_sha256(self): """Test SHA256 checksum calculation.""" from clients.garmin_client import calculate_sha256 # Create test file test_file = self.test_data_dir / "test.txt" test_content = b"Hello, world! This is a test file for SHA256 calculation." test_file.write_bytes(test_content) # Calculate checksum checksum = calculate_sha256(test_file) # Verify the checksum is correct import hashlib expected_checksum = hashlib.sha256(test_content).hexdigest() assert checksum == expected_checksum def test_should_skip_download_exists_and_matches(self): """Test skip logic when file exists and checksum matches.""" from clients.garmin_client import should_skip_download activity_id = 22222 # Create test file test_file = self.test_data_dir / "activity_22222.fit" test_content = b"test workout data" test_file.write_bytes(test_content) # Create database record with matching checksum with SessionLocal() as db: record = ActivityDownload( activity_id=activity_id, source="garmin-connect", file_path=str(test_file), file_format="fit", status="success", size_bytes=len(test_content), checksum_sha256=calculate_sha256(test_file) ) db.add(record) db.commit() # Test should skip with SessionLocal() as db: should_skip, reason = should_skip_download(activity_id, db) assert should_skip is True assert "already downloaded" in reason.lower() def test_should_skip_download_exists_checksum_mismatch(self): """Test skip logic when file exists but checksum doesn't match.""" from clients.garmin_client import should_skip_download, calculate_sha256 activity_id = 33333 # Create test file with different content than recorded test_file = self.test_data_dir / "activity_33333.fit" test_content = b"new workout data - different from recorded" test_file.write_bytes(test_content) # Create database record with different checksum with SessionLocal() as db: record = ActivityDownload( activity_id=activity_id, source="garmin-connect", file_path=str(test_file), file_format="fit", status="success", size_bytes=100, # Different size checksum_sha256="old_mismatched_checksum_12345" ) db.add(record) db.commit() # Test should not skip with SessionLocal() as db: should_skip, reason = should_skip_download(activity_id, db) assert should_skip is False assert "checksum mismatch" in reason.lower() def test_should_skip_download_file_missing(self): """Test skip logic when database record exists but file is missing.""" from clients.garmin_client import should_skip_download activity_id = 44444 # Create database record but no actual file with SessionLocal() as db: record = ActivityDownload( activity_id=activity_id, source="garmin-connect", file_path=str(self.test_data_dir / "missing_activity.fit"), file_format="fit", status="success", size_bytes=100, checksum_sha256="some_checksum" ) db.add(record) db.commit() # Test should not skip with SessionLocal() as db: should_skip, reason = should_skip_download(activity_id, db) assert should_skip is False assert "file missing" in reason.lower() def test_should_skip_download_no_record(self): """Test skip logic when no database record exists.""" from clients.garmin_client import should_skip_download activity_id = 55555 # No record in database with SessionLocal() as db: should_skip, reason = should_skip_download(activity_id, db) assert should_skip is False assert "no record" in reason.lower() @patch('clients.garmin_client.GarminClient.authenticate') @patch('clients.garmin_client.GarminClient.download_activity_original') def test_download_activity_with_db_integration(self, mock_download, mock_authenticate): """Test download_activity_original with database integration.""" mock_authenticate.return_value = True # Create test file content test_content = b"FIT file content for testing" test_file = self.test_data_dir / "activity_66666.fit" # Mock the download to return our test file path mock_download.return_value = test_file # Create the test file test_file.write_bytes(test_content) # Create client and test download client = GarminClient() result = client.download_activity_original("66666") # Verify download was called mock_download.assert_called_once_with("66666", force_download=False, db_session=None) # Verify database record was created with SessionLocal() as db: record = db.query(ActivityDownload).filter_by(activity_id=66666).first() assert record is not None assert record.status == "success" assert record.size_bytes == len(test_content) assert record.checksum_sha256 is not None def test_force_download_override(self): """Test that force_download=True overrides skip logic.""" from clients.garmin_client import should_skip_download activity_id = 77777 # Create existing record that would normally cause skip with SessionLocal() as db: record = ActivityDownload( activity_id=activity_id, source="garmin-connect", file_path=str(self.test_data_dir / "activity_77777.fit"), file_format="fit", status="success", size_bytes=100, checksum_sha256="valid_checksum" ) db.add(record) db.commit() # Create the file too test_file = self.test_data_dir / "activity_77777.fit" test_file.write_bytes(b"test content") # Test with force_download=False (should skip) with SessionLocal() as db: should_skip, reason = should_skip_download(activity_id, db, force_download=False) assert should_skip is True # Test with force_download=True (should not skip) with SessionLocal() as db: should_skip, reason = should_skip_download(activity_id, db, force_download=True) assert should_skip is False assert "force download" in reason.lower() if __name__ == "__main__": pytest.main([__file__, "-v"])