mirror of
https://github.com/sstent/FitTrack_GarminSync.git
synced 2026-01-28 01:52:24 +00:00
430 lines
18 KiB
Python
430 lines
18 KiB
Python
"""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"]) |