mirror of
https://github.com/sstent/FitTrack_GarminSync.git
synced 2026-01-29 02:22:16 +00:00
feat: Add files from examples/Garmin_Analyser
This commit is contained in:
430
examples/Garmin_Analyser/tests/test_download_missing.py
Normal file
430
examples/Garmin_Analyser/tests/test_download_missing.py
Normal file
@@ -0,0 +1,430 @@
|
||||
"""Tests for missing download functionality with --missing and --dry-run options."""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
|
||||
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 TestDownloadMissing:
|
||||
"""Test missing download functionality with --missing and --dry-run options."""
|
||||
|
||||
@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())
|
||||
|
||||
# Patch settings to use test paths and in-memory database
|
||||
with patch('config.settings.DATA_DIR', self.test_data_dir), \
|
||||
patch('config.settings.DB_PATH', "sqlite:///:memory:"):
|
||||
|
||||
# Recreate engine with the patched DB_PATH
|
||||
from sqlalchemy import create_engine
|
||||
from db.session import Base
|
||||
self.engine = create_engine("sqlite:///:memory:")
|
||||
Base.metadata.create_all(bind=self.engine)
|
||||
|
||||
# Patch the engine in the session module
|
||||
with patch('db.session.engine', self.engine):
|
||||
yield
|
||||
|
||||
# Clean up
|
||||
if self.test_data_dir.exists():
|
||||
shutil.rmtree(self.test_data_dir)
|
||||
|
||||
def _clean_database(self):
|
||||
"""Clean the database between tests to avoid conflicts."""
|
||||
from db.session import Base
|
||||
Base.metadata.drop_all(bind=self.engine)
|
||||
Base.metadata.create_all(bind=self.engine)
|
||||
|
||||
def test_get_downloaded_activity_ids_from_db(self):
|
||||
"""Test getting downloaded activity IDs from database."""
|
||||
self._clean_database()
|
||||
client = GarminClient()
|
||||
|
||||
# Create test records
|
||||
activity_ids = [11111, 22222, 33333]
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in activity_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test getting downloaded IDs
|
||||
with SessionLocal() as db:
|
||||
downloaded_ids = client.get_downloaded_activity_ids(db)
|
||||
assert set(downloaded_ids) == set(activity_ids)
|
||||
|
||||
def test_get_downloaded_activity_ids_empty_db(self):
|
||||
"""Test getting downloaded activity IDs from empty database."""
|
||||
client = GarminClient()
|
||||
|
||||
with SessionLocal() as db:
|
||||
downloaded_ids = client.get_downloaded_activity_ids(db)
|
||||
assert downloaded_ids == []
|
||||
|
||||
def test_get_downloaded_activity_ids_mixed_status(self):
|
||||
"""Test getting downloaded activity IDs with mixed statuses."""
|
||||
client = GarminClient()
|
||||
|
||||
# Create test records with different statuses
|
||||
with SessionLocal() as db:
|
||||
# Success records
|
||||
success_ids = [11111, 22222]
|
||||
for activity_id in success_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
|
||||
# Failed record (should not be included)
|
||||
failed_record = ActivityDownload(
|
||||
activity_id=33333,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / "activity_33333.fit"),
|
||||
file_format="fit",
|
||||
status="failed",
|
||||
error_message="Download failed",
|
||||
size_bytes=0,
|
||||
checksum_sha256=None
|
||||
)
|
||||
db.add(failed_record)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Test getting downloaded IDs (only success status)
|
||||
with SessionLocal() as db:
|
||||
downloaded_ids = client.get_downloaded_activity_ids(db)
|
||||
assert set(downloaded_ids) == set(success_ids)
|
||||
assert 33333 not in downloaded_ids
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_find_missing_activities(self, mock_get_all_activities):
|
||||
"""Test finding missing activities."""
|
||||
# Mock all activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": 11111, "activityName": "Ride 1"},
|
||||
{"activityId": 22222, "activityName": "Ride 2"},
|
||||
{"activityId": 33333, "activityName": "Ride 3"},
|
||||
{"activityId": 44444, "activityName": "Ride 4"}
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Create some downloaded records
|
||||
downloaded_ids = [11111, 33333] # Missing: 22222, 44444
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test finding missing activities
|
||||
client = GarminClient()
|
||||
missing_activities = client.find_missing_activities(db)
|
||||
|
||||
expected_missing = [
|
||||
{"activityId": 22222, "activityName": "Ride 2"},
|
||||
{"activityId": 44444, "activityName": "Ride 4"}
|
||||
]
|
||||
|
||||
assert len(missing_activities) == 2
|
||||
assert missing_activities == expected_missing
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_find_missing_activities_none_missing(self, mock_get_all_activities):
|
||||
"""Test finding missing activities when none are missing."""
|
||||
# Mock all activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": 11111, "activityName": "Ride 1"},
|
||||
{"activityId": 22222, "activityName": "Ride 2"}
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Create downloaded records for all activities
|
||||
downloaded_ids = [11111, 22222]
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test finding missing activities
|
||||
client = GarminClient()
|
||||
missing_activities = client.find_missing_activities(db)
|
||||
|
||||
assert len(missing_activities) == 0
|
||||
assert missing_activities == []
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_find_missing_activities_limit(self, mock_get_all_activities):
|
||||
"""Test finding missing activities with limit."""
|
||||
# Mock many activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": i, "activityName": f"Ride {i}"} for i in range(1, 11)
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Create some downloaded records
|
||||
downloaded_ids = [1, 3, 5, 7, 9] # Missing: 2, 4, 6, 8, 10
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test finding missing activities with limit
|
||||
client = GarminClient()
|
||||
missing_activities = client.find_missing_activities(db, limit=3)
|
||||
|
||||
assert len(missing_activities) == 3
|
||||
# Should return the first 3 missing activities (2, 4, 6)
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.download_activity_original')
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_download_missing_activities_dry_run(self, mock_get_all_activities, mock_download):
|
||||
"""Test downloading missing activities in dry-run mode."""
|
||||
# Mock all activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": 11111, "activityName": "Ride 1"},
|
||||
{"activityId": 22222, "activityName": "Ride 2"},
|
||||
{"activityId": 33333, "activityName": "Ride 3"}
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Create some downloaded records
|
||||
downloaded_ids = [11111] # Missing: 22222, 33333
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test downloading missing activities in dry-run mode
|
||||
client = GarminClient()
|
||||
result = client.download_missing_activities(db, dry_run=True)
|
||||
|
||||
# Should return the missing activities but not download them
|
||||
assert len(result["missing_activities"]) == 2
|
||||
assert result["downloaded_count"] == 0
|
||||
assert result["dry_run"] is True
|
||||
|
||||
# Download should not be called in dry-run mode
|
||||
mock_download.assert_not_called()
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.download_activity_original')
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_download_missing_activities_real_run(self, mock_get_all_activities, mock_download):
|
||||
"""Test downloading missing activities in real run mode."""
|
||||
# Mock all activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": 11111, "activityName": "Ride 1"},
|
||||
{"activityId": 22222, "activityName": "Ride 2"},
|
||||
{"activityId": 33333, "activityName": "Ride 3"}
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Mock download to return a test file path
|
||||
def mock_download_func(activity_id, **kwargs):
|
||||
test_file = self.test_data_dir / f"activity_{activity_id}.fit"
|
||||
test_file.write_bytes(b"test content")
|
||||
return test_file
|
||||
|
||||
mock_download.side_effect = mock_download_func
|
||||
|
||||
# Create some downloaded records
|
||||
downloaded_ids = [11111] # Missing: 22222, 33333
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test downloading missing activities in real run mode
|
||||
client = GarminClient()
|
||||
result = client.download_missing_activities(db, dry_run=False)
|
||||
|
||||
# Should return the missing activities and download them
|
||||
assert len(result["missing_activities"]) == 2
|
||||
assert result["downloaded_count"] == 2
|
||||
assert result["dry_run"] is False
|
||||
|
||||
# Download should be called for each missing activity
|
||||
assert mock_download.call_count == 2
|
||||
mock_download.assert_has_calls([
|
||||
call("22222", force_download=False, db_session=db),
|
||||
call("33333", force_download=False, db_session=db)
|
||||
], any_order=True)
|
||||
|
||||
# Verify database records were created for downloaded activities
|
||||
with SessionLocal() as db:
|
||||
records = db.query(ActivityDownload).all()
|
||||
assert len(records) == 3 # Original + 2 new downloads
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.download_activity_original')
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_download_missing_activities_with_limit(self, mock_get_all_activities, mock_download):
|
||||
"""Test downloading missing activities with limit."""
|
||||
# Mock many activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": i, "activityName": f"Ride {i}"} for i in range(1, 11)
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Mock download to return a test file path
|
||||
def mock_download_func(activity_id, **kwargs):
|
||||
test_file = self.test_data_dir / f"activity_{activity_id}.fit"
|
||||
test_file.write_bytes(b"test content")
|
||||
return test_file
|
||||
|
||||
mock_download.side_effect = mock_download_func
|
||||
|
||||
# Create some downloaded records
|
||||
downloaded_ids = [1, 3, 5, 7, 9] # Missing: 2, 4, 6, 8, 10
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test downloading missing activities with limit
|
||||
client = GarminClient()
|
||||
result = client.download_missing_activities(db, dry_run=False, limit=2)
|
||||
|
||||
# Should return the missing activities and download only up to limit
|
||||
assert len(result["missing_activities"]) == 5 # All missing are identified
|
||||
assert result["downloaded_count"] == 2 # But only 2 are downloaded due to limit
|
||||
assert result["dry_run"] is False
|
||||
|
||||
# Download should be called only for the limit
|
||||
assert mock_download.call_count == 2
|
||||
|
||||
@patch('clients.garmin_client.GarminClient.download_activity_original')
|
||||
@patch('clients.garmin_client.GarminClient.get_all_activities')
|
||||
def test_download_missing_activities_with_force(self, mock_get_all_activities, mock_download):
|
||||
"""Test downloading missing activities with force option."""
|
||||
# Mock all activities from Garmin
|
||||
all_activities = [
|
||||
{"activityId": 11111, "activityName": "Ride 1"},
|
||||
{"activityId": 22222, "activityName": "Ride 2"}
|
||||
]
|
||||
mock_get_all_activities.return_value = all_activities
|
||||
|
||||
# Mock download to return a test file path
|
||||
def mock_download_func(activity_id, force_download=False, **kwargs):
|
||||
test_file = self.test_data_dir / f"activity_{activity_id}.fit"
|
||||
test_file.write_bytes(b"test content")
|
||||
return test_file
|
||||
|
||||
mock_download.side_effect = mock_download_func
|
||||
|
||||
# Create downloaded records for all activities (should still download with force)
|
||||
downloaded_ids = [11111, 22222]
|
||||
|
||||
with SessionLocal() as db:
|
||||
for activity_id in downloaded_ids:
|
||||
record = ActivityDownload(
|
||||
activity_id=activity_id,
|
||||
source="garmin-connect",
|
||||
file_path=str(self.test_data_dir / f"activity_{activity_id}.fit"),
|
||||
file_format="fit",
|
||||
status="success",
|
||||
size_bytes=100,
|
||||
checksum_sha256=f"checksum_{activity_id}"
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
# Test downloading with force=True (should download all even if they exist)
|
||||
client = GarminClient()
|
||||
result = client.download_missing_activities(db, dry_run=False, force=True)
|
||||
|
||||
# Should download all activities with force
|
||||
assert result["downloaded_count"] == 2
|
||||
assert mock_download.call_count == 2
|
||||
|
||||
# Download should be called with force_download=True
|
||||
mock_download.assert_has_calls([
|
||||
call("11111", force_download=True, db_session=db),
|
||||
call("22222", force_download=True, db_session=db)
|
||||
], any_order=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user